use std::{
    alloc::Layout,
    collections::HashMap,
    env::temp_dir,
    fs::{self, OpenOptions},
    mem::size_of,
    path::PathBuf,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc, LazyLock, Mutex,
    },
};

use dfs::{
    dfs_read,
    inode::{DfsInode, DirCreateOptions},
    params::{ParamCreateOptions, ParamOperation},
    seq_file::{SeqFileCreateOptions, SeqFileOperation},
    seq_print, seq_println,
};
use rcu::{call_rcu, RcuLock};

// trace 激活
const TRACE_ENABLE: Option<&'static str> = option_env!("SIMINK_TRACE_ENABLE");

/// trace 是否激活
///
/// 不需要手动调用, 定义 `tracelog` 后自动使用
pub const fn tracelog_enable() -> bool {
    TRACE_ENABLE.is_some()
}

/// trace 元数据
///
/// 记录 trace 元数据, 每一个 `tracelog` 被记录都会生成一个元数据并被存储
pub struct TraceMetaData {
    /// 元数据记录地址
    ///
    /// 元数据被释放时自动释放该地址
    pub data: *mut u8,
    layout: Layout,
    fmt: fn(&TraceMetaData) -> String,
}

unsafe impl Send for TraceMetaData {}
unsafe impl Sync for TraceMetaData {}

impl TraceMetaData {
    /// 创建一个新的元数据定义结构体
    ///
    /// 记录一个 tracelog 则会生成一个 tracemeta
    #[allow(clippy::missing_panics_doc)]
    #[inline]
    pub fn create(size: usize, fmt: fn(&TraceMetaData) -> String) -> Self {
        if tracelog_enable() {
            let layout = Layout::from_size_align(size, size_of::<usize>()).unwrap();
            unsafe { Self { data: std::alloc::alloc(layout), layout, fmt } }
        } else {
            let layout = Layout::from_size_align(0, 0).unwrap();

            Self { data: std::ptr::null_mut::<u8>(), layout, fmt }
        }
    }
}

impl Drop for TraceMetaData {
    fn drop(&mut self) {
        if tracelog_enable() {
            unsafe {
                std::alloc::dealloc(self.data, self.layout);
            }
        }
    }
}

static TRACE_METADATA: RcuLock<Vec<TraceMetaData>> = RcuLock::new(Vec::new());

/// 存放一个 tracemeta
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn trace_metadata_push(meta: TraceMetaData) {
    if tracelog_enable() {
        let mut lock = TRACE_METADATA.write().unwrap();
        lock.push(meta);
    }
}

/// 遍历 trace 记录
///
/// 当用户查看 trace 信息时使用该函数遍历 trace 信息
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn trace_metadata_for_each<F>(mut f: F)
where
    F: FnMut(String),
{
    if tracelog_enable() {
        let lock = TRACE_METADATA.read().unwrap();
        lock.iter().for_each(|meta| {
            let s = (meta.fmt)(meta);
            f(s);
        });
    }
}

// 首先键值是 class, 第二个 hash 是 event
#[allow(clippy::type_complexity)]
static TRACE_POINTER: LazyLock<
    Mutex<HashMap<String, (AtomicBool, HashMap<String, Arc<AtomicBool>>)>>,
> = LazyLock::new(|| Mutex::new(HashMap::new()));

/// 定义一个 trace 点
#[allow(clippy::missing_panics_doc)]
#[inline]
pub fn trace_point_push(class: String, event: String, status: Arc<AtomicBool>) {
    if tracelog_enable() {
        let mut lock = TRACE_POINTER.lock().unwrap();

        let cls = lock.get_mut(&class);
        if let Some((_, events)) = cls {
            events.insert(event, status);
        } else {
            let mut ev_hash = HashMap::new();
            ev_hash.insert(event, status);
            lock.insert(class, (AtomicBool::new(false), ev_hash));
        }
    }
}

#[derive(Debug, Default)]
struct FilePathSet {
    path: Mutex<PathBuf>,
}

impl ParamOperation for FilePathSet {
    fn read(&self) -> String {
        let lock = self.path.lock().unwrap();
        lock.to_str().unwrap().to_string()
    }

    fn wrtie(&self, buf: &str) -> dfs::DfsResult<()> {
        let path =
            if buf.is_empty() { temp_dir().join("simink.trace") } else { PathBuf::from(buf) };

        let res = OpenOptions::new().read(true).write(true).create(true).open(&path);
        match res {
            Ok(_) => {
                let mut lock = self.path.lock().unwrap();
                *lock = path;
                Ok(())
            },
            Err(_) => Err(dfs::DfsError::InvalidArgs),
        }
    }
}

#[derive(Debug, Default)]
struct TraceManages {
    enable: AtomicBool,
}

impl ParamOperation for TraceManages {
    fn read(&self) -> String {
        self.enable.load(Ordering::Relaxed).to_string()
    }

    fn wrtie(&self, buf: &str) -> dfs::DfsResult<()> {
        let enable = buf.parse::<bool>().map_err(|_| dfs::DfsError::InvalidArgs)?;
        let lock = TRACE_POINTER.lock().unwrap();
        lock.iter().for_each(|(_, (status, events))| {
            for (_, pointer) in events.iter() {
                pointer.store(enable, Ordering::Relaxed);
            }
            status.store(enable, Ordering::Relaxed);
        });
        self.enable.store(enable, Ordering::Relaxed);

        Ok(())
    }
}

#[derive(Debug, Default)]
struct ClassManages {
    class: String,
}

impl ParamOperation for ClassManages {
    fn read(&self) -> String {
        let lock = TRACE_POINTER.lock().unwrap();
        let cls = lock.get(&self.class).unwrap();
        cls.0.load(Ordering::Relaxed).to_string()
    }

    fn wrtie(&self, buf: &str) -> dfs::DfsResult<()> {
        let enable = buf.parse::<bool>().map_err(|_| dfs::DfsError::InvalidArgs)?;
        let lock = TRACE_POINTER.lock().unwrap();
        let cls = lock.get(&self.class).unwrap();
        cls.1.iter().for_each(|(_, pointer)| {
            pointer.store(enable, Ordering::Relaxed);
        });
        cls.0.store(enable, Ordering::Relaxed);
        Ok(())
    }
}

#[derive(Debug, Default)]
struct EventManages {
    class: String,
    event: String,
}

impl ParamOperation for EventManages {
    fn read(&self) -> String {
        let lock = TRACE_POINTER.lock().unwrap();
        let cls = lock.get(&self.class).unwrap();
        let event = cls.1.get(&self.event).unwrap();
        event.load(Ordering::Relaxed).to_string()
    }

    fn wrtie(&self, buf: &str) -> dfs::DfsResult<()> {
        let enable = buf.parse::<bool>().map_err(|_| dfs::DfsError::InvalidArgs)?;
        let lock = TRACE_POINTER.lock().unwrap();
        let cls = lock.get(&self.class).unwrap();
        let event = cls.1.get(&self.event).unwrap();
        event.store(enable, Ordering::Relaxed);
        Ok(())
    }
}

fn create_events_manage(events_dir: Arc<DfsInode>) {
    ParamCreateOptions::new()
        .read_write(true)
        .name("enable")
        .create_param_file(
            events_dir.clone(),
            Arc::new(TraceManages { enable: AtomicBool::new(false) }),
        )
        .unwrap();

    let class = TRACE_POINTER.lock().unwrap();
    class.iter().for_each(|(class, events)| {
        let class_dir = DirCreateOptions::new()
            .read_write(true)
            .name(class)
            .create_dir(events_dir.clone())
            .unwrap();
        ParamCreateOptions::new()
            .read_write(true)
            .name("enable")
            .create_param_file(class_dir.clone(), Arc::new(ClassManages { class: class.clone() }))
            .unwrap();
        events.1.iter().for_each(|(event, _)| {
            let event_dir = DirCreateOptions::new()
                .read_write(true)
                .name(event)
                .create_dir(class_dir.clone())
                .unwrap();
            ParamCreateOptions::new()
                .read_write(true)
                .name("enable")
                .create_param_file(
                    event_dir,
                    Arc::new(EventManages { class: class.clone(), event: event.clone() }),
                )
                .unwrap();
        });
    });
}

struct TracingEvent;

impl SeqFileOperation for TracingEvent {
    fn show(
        &self,
        m: &mut dfs::seq_file::SeqFile,
        _: &mut Box<dyn std::any::Any + Send>,
    ) -> dfs::DfsResult<()> {
        let mut buf = String::new();
        let mut all_enable = true;
        let lock = TRACE_POINTER.lock().unwrap();
        lock.iter().for_each(|(class, events)| {
            events.1.iter().for_each(|(event, status)| {
                if status.load(Ordering::Relaxed) {
                    buf.push_str(&format!("{class}:{event}\n"));
                } else {
                    all_enable = false;
                }
            });
        });
        if !buf.is_empty() {
            if all_enable {
                seq_print!(m, "All event enabled");
            } else {
                seq_print!(m, "{}", buf);
            }
        }
        Ok(())
    }
}

struct TraceShow;

impl SeqFileOperation for TraceShow {
    fn show(
        &self,
        m: &mut dfs::seq_file::SeqFile,
        _: &mut Box<dyn std::any::Any + Send>,
    ) -> dfs::DfsResult<()> {
        trace_metadata_for_each(|s| {
            seq_println!(m, "{}", s);
        });
        Ok(())
    }

    fn write(&self, _buf: &str, pos: &mut u64) -> dfs::DfsResult<usize> {
        let mut lock = TRACE_METADATA.write().unwrap();
        let meta = lock.extract_if(|_| true).collect::<Vec<TraceMetaData>>();
        call_rcu(|| drop(meta));
        Ok(*pos as usize)
    }
}

pub(crate) fn trace_init(sys: Arc<DfsInode>) {
    if tracelog_enable() {
        let trace_dir =
            DirCreateOptions::new().read_write(true).name("tracing").create_dir(sys).unwrap();

        let path = Arc::new(FilePathSet::default());
        ParamCreateOptions::new()
            .read_write(true)
            .name("trace_file_path")
            .create_param_file(trace_dir.clone(), path)
            .unwrap();

        let events_dir = DirCreateOptions::new()
            .read_write(true)
            .name("events")
            .create_dir(trace_dir.clone())
            .unwrap();
        create_events_manage(events_dir);
        SeqFileCreateOptions::new()
            .read(true)
            .name("current_event")
            .create_seq_file(trace_dir.clone(), Arc::new(TracingEvent))
            .unwrap();

        SeqFileCreateOptions::new()
            .read_write(true)
            .name("trace")
            .create_seq_file(trace_dir.clone(), Arc::new(TraceShow))
            .unwrap();
    }
}

pub(crate) fn trace_exit() {
    if tracelog_enable() {
        let mut buf = String::new();
        let size = dfs_read("/sys/tracing/trace_file_path", &mut buf).unwrap();
        if size == 0 {
            return;
        }
        let path = PathBuf::from(buf);
        trace_metadata_for_each(|s| {
            fs::write(&path, s.as_bytes()).unwrap();
        });
    }
}
